#include "Model/Requirement.hpp"
#include "Model/Package.hpp"
#include "Model/DuplicateNameError.hpp"
#include "Model/InvalidHierarchyOperationError.hpp"
#include "Model/InvalidNameError.hpp"
#include <boost/test/auto_unit_test.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/tuple/tuple.hpp>

using namespace Model;


namespace
{

struct TestFixture
{
	static const std::vector< std::string> REQUIREMENT_NAMES;
	static const std::vector< std::string> REQUIREMENT_STATEMENTS;
private:
	static const std::vector< std::string> CREATE_REQUIREMENT_NAMES( std::size_t count)
	{
		std::vector< std::string> result;
		result.reserve( count);
		static const std::string NAME_ROOT = "Requirement ";
		for( std::size_t i = 0; i < count; ++i)
		{
			result.push_back( NAME_ROOT + boost::lexical_cast< std::string>( i + 1));
		}
		return result;
	}

	static const std::vector< std::string> CREATE_REQUIREMENT_STATEMENTS( std::size_t count)
	{
		std::vector< std::string> result;
		result.reserve( count);
		static const std::string STATEMENT_ROOT = "Requirement ";
		for( std::size_t i = 0; i < count; ++i)
		{
			result.push_back( STATEMENT_ROOT + boost::lexical_cast< std::string>( i + 1));
		}
		return result;
	}
};

const std::vector< std::string> TestFixture::REQUIREMENT_NAMES 
		= TestFixture::CREATE_REQUIREMENT_NAMES( 10);
const std::vector< std::string> TestFixture::REQUIREMENT_STATEMENTS 
		= TestFixture::CREATE_REQUIREMENT_STATEMENTS( 10);


// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testInitialization)
{
	TestFixture fixture;
	Requirement rootRq( "ROOT", "STATEMENT");
	BOOST_CHECK_EQUAL( "ROOT", rootRq.name());
	BOOST_CHECK_EQUAL( "STATEMENT", rootRq.statement());
	BOOST_CHECK_EQUAL( 0, rootRq.position());

	BOOST_CHECKPOINT( "Checking initialization with empty name");
	BOOST_CHECK_THROW( Requirement( ""), InvalidNameError);
}

//--------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testAddingNewChildRequirements)
{
	TestFixture fixture;
	Requirement rootRq( "ROOT");

	// Test first add:
	Requirement* r1 = rootRq.addChildRequirement( fixture.REQUIREMENT_NAMES[0], 1, fixture.REQUIREMENT_STATEMENTS[0]);
		// Check addition
	BOOST_CHECK( r1 == *rootRq.childrenBegin());
		// Check initialization
	BOOST_CHECK_EQUAL( fixture.REQUIREMENT_NAMES[0], r1->name());
	BOOST_CHECK_EQUAL( fixture.REQUIREMENT_STATEMENTS[0], r1->statement());
	BOOST_CHECK_EQUAL( 1, r1->position());
	BOOST_CHECK( &rootRq == &r1->parent());

	// Test addition to the end
	Requirement* r3 = rootRq.addChildRequirement( fixture.REQUIREMENT_NAMES[1], 10, fixture.REQUIREMENT_STATEMENTS[1]);
	BOOST_REQUIRE_EQUAL( 2, rootRq.numberOfChildren());
	BOOST_CHECK( r3 == *(++rootRq.childrenBegin()));
		// Check position
	BOOST_CHECK_EQUAL( 2, r3->position());

	// Test addition to the middle
	Requirement* r2 = rootRq.addChildRequirement( fixture.REQUIREMENT_NAMES[2], 2, fixture.REQUIREMENT_STATEMENTS[2]);
	BOOST_REQUIRE_EQUAL( 3, rootRq.numberOfChildren());
	BOOST_CHECK( r2 == *(++rootRq.childrenBegin()));
		// Check position
	BOOST_CHECK_EQUAL( 2, r2->position());
		// Check position updated on the rest of the children to the end of the list.
	BOOST_CHECK_EQUAL( 3, r3->position());

	// Test addition to the beginning
	Requirement* r0 = rootRq.addChildRequirement( fixture.REQUIREMENT_NAMES[3], 1, fixture.REQUIREMENT_STATEMENTS[3]);
	BOOST_REQUIRE_EQUAL( 4, rootRq.numberOfChildren());
	BOOST_CHECK( r0 == *rootRq.childrenBegin());
	BOOST_CHECK_EQUAL( 1, r0->position());
		// Check positions
	BOOST_CHECK_EQUAL( 2, r1->position());
	BOOST_CHECK_EQUAL( 3, r2->position());
	BOOST_CHECK_EQUAL( 4, r3->position());

	// Test addition of duplicate name
	Requirement* rDup = rootRq.addChildRequirement( fixture.REQUIREMENT_NAMES[0], 10, fixture.REQUIREMENT_STATEMENTS[4]);
	BOOST_REQUIRE_EQUAL( 5, rootRq.numberOfChildren());
	BOOST_CHECK( r0 == *rootRq.childrenBegin());
	BOOST_CHECK_EQUAL( 1, r0->position());
	// Check positions
	BOOST_CHECK_EQUAL( 2, r1->position());
	BOOST_CHECK_EQUAL( 3, r2->position());
	BOOST_CHECK_EQUAL( 4, r3->position());
	BOOST_CHECK_EQUAL( 5, rDup->position());
}

//--------------------------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( testMovingChildRequirements)
{
	TestFixture fixture;
	Requirement rootRq( "ROOT");

	// Preparation of structures:
	// root
	//	r1
	//		r11
	//			r111
	//			r112
	//		r12
	//  r2
	//		r21
	//		r22
	//	r3

	Requirement* r1 = rootRq.addChildRequirement( "R1", 1);
	Requirement* r11 = r1->addChildRequirement( "R11", 1);
	Requirement* r111 = r11->addChildRequirement( "R111", 1);
	Requirement* r112 = r11->addChildRequirement( "R112", 2);
	Requirement* r12 = r1->addChildRequirement( "R12", 2);
	Requirement* r2 = rootRq.addChildRequirement( "R2", 2);
	Requirement* r21 = r2->addChildRequirement( "R21", 1);
	Requirement* r22 = r2->addChildRequirement( "R22", 2);
	Requirement* r3 = rootRq.addChildRequirement( "R3", 3);

	// Test move r11 from r1 to r2, in the middle (pos2)
	BOOST_CHECKPOINT( "Test move r11 from r1 to r2, in the middle (pos2)");

	r2->addChildRequirement( r11, 2);
		// Check parent changed
	BOOST_CHECK( r2 == &r11->parent());
		// Check position changed
	BOOST_CHECK_EQUAL( 2, r11->position());
		// Check position of old siblings changed
	BOOST_CHECK_EQUAL( 1, r12->position());
		// Check position of the new siblings changed
	BOOST_CHECK_EQUAL( 1, r21->position());
	BOOST_CHECK_EQUAL( 3, r22->position());	

	// Some cleanup
	r2->removeChildRequirement( r11);

	// Test move orphan requirement
	BOOST_CHECKPOINT( "Test move orphan requirement");
		// First... make the requirement orphan
	r1->addChildRequirement( r11, 1);
		// Check parent changed
	BOOST_CHECK( r1 == &r11->parent());
		// Check position changed
	BOOST_CHECK_EQUAL( 1, r11->position());
		// Check position of new siblings changed
	BOOST_CHECK_EQUAL( 2, r12->position());

	// Test move inside the same parents - forward
	BOOST_CHECKPOINT( "Test move inside the same parents - forward");
	rootRq.addChildRequirement( r1, 2);
		// Check parent changed
	BOOST_CHECK( &rootRq == &r1->parent());
		// Check position changed
	BOOST_CHECK_EQUAL( 2, r1->position());
		// Check position of new siblings changed
	BOOST_CHECK_EQUAL( 1, r2->position());
	BOOST_CHECK_EQUAL( 3, r3->position());

	// Test move inside the same parents - backward
	BOOST_CHECKPOINT( "Test move inside the same parents - backward");
	rootRq.addChildRequirement( r1, 1);
	// Check parent changed
	BOOST_CHECK( &rootRq == &r1->parent());
	// Check position changed
	BOOST_CHECK_EQUAL( 1, r1->position());
	// Check position of new siblings changed
	BOOST_CHECK_EQUAL( 2, r2->position());
	BOOST_CHECK_EQUAL( 3, r3->position());


	// Test move inside the same parents - no position changed
	BOOST_CHECKPOINT( "Test move inside the same parents - no position changed");
	rootRq.addChildRequirement( r1, 1);
	// Check parent changed
	BOOST_CHECK( &rootRq == &r1->parent());
	// Check position changed
	BOOST_CHECK_EQUAL( 1, r1->position());


	// Test duplicate name
	BOOST_CHECKPOINT( "Test duplicate name");
	r1->setName( "R21");
	BOOST_CHECK_THROW( r2->addChildRequirement( r1, 1), DuplicateNameError);

	// Test invalid hierarchy operation
	BOOST_CHECKPOINT( "Test invalid hierarchy operation");
	BOOST_CHECK_THROW( r111->addChildRequirement( r1, 1), InvalidHierarchyOperationError);
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testAddingChildRequirementsWhenHavingPackage)
{
	TestFixture fixture;

	Package package( "Parent Package");
	Requirement rootRq( "ROOT");
	rootRq.moveToPackage( &package);
	// root
	//   r1
	//     r11
	//     r12
	//   r2
	Requirement* r1 = rootRq.addChildRequirement( "R1", 1);
	Requirement* r11 = r1->addChildRequirement( "R11", 1);
	Requirement* r12 = r1->addChildRequirement( "R12", 2);
	Requirement* r2 = rootRq.addChildRequirement( "R2", 2);

	BOOST_CHECK_EQUAL( &package, rootRq.package());
	BOOST_CHECK_EQUAL( &package, r1->package());
	BOOST_CHECK_EQUAL( &package, r11->package());
	BOOST_CHECK_EQUAL( &package, r12->package());
	BOOST_CHECK_EQUAL( &package, r2->package());
}

//--------------------------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( testStatementChange)
{
	TestFixture fixture;
	Requirement rootRq( "ROOT");

	Requirement* r1 = rootRq.addChildRequirement( fixture.REQUIREMENT_NAMES[0], 1, fixture.REQUIREMENT_STATEMENTS[0]);
	r1->setStatement( fixture.REQUIREMENT_STATEMENTS[1]);
	BOOST_CHECK_EQUAL( fixture.REQUIREMENT_STATEMENTS[1], r1->statement());
}


//--------------------------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( testStatementDelete)
{
	TestFixture fixture;
	Requirement rootRq( "ROOT");

	Requirement* r1 = rootRq.addChildRequirement( fixture.REQUIREMENT_NAMES[0], 1, fixture.REQUIREMENT_STATEMENTS[0]);
	Requirement* r11 = r1->addChildRequirement( fixture.REQUIREMENT_NAMES[1], 1, fixture.REQUIREMENT_STATEMENTS[1]);
	Requirement* r12 = r1->addChildRequirement( fixture.REQUIREMENT_NAMES[2], 2, fixture.REQUIREMENT_STATEMENTS[2]);
	Requirement* r2 = rootRq.addChildRequirement( fixture.REQUIREMENT_NAMES[3], 2, fixture.REQUIREMENT_STATEMENTS[3]);
	
	BOOST_CHECK( r1 == rootRq.removeChildRequirement( r1));
	// Check not in the parent anymore
	BOOST_CHECK( rootRq.childrenEnd() == std::find( rootRq.childrenBegin(), rootRq.childrenEnd(), r1));
	// Check position change for the next siblings
	BOOST_CHECK_EQUAL( 1, r2->position());

	// Cleanup
	delete r1;
}


//--------------------------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( testFindByName)
{
	TestFixture fixture;
	Requirement rootRq( "ROOT");

	Requirement* r1 = rootRq.addChildRequirement( "R1", 1);
	Requirement* r2 = rootRq.addChildRequirement( "R2", 2);
	Requirement* r3 = rootRq.addChildRequirement( "R3", 3);

	BOOST_CHECKPOINT( "Test finding existing name");
	BOOST_CHECK( r2 == rootRq.findByName( "R2"));

	BOOST_CHECKPOINT( "Test finding inexistent name");
	BOOST_CHECK( NULL == rootRq.findByName( "Inexistent"));
}


//--------------------------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( testHasChildWithName)
{
	TestFixture fixture;
	Requirement rootRq( "ROOT");

	Requirement* r1 = rootRq.addChildRequirement( "R1", 1);
	Requirement* r2 = rootRq.addChildRequirement( "R2", 2);
	Requirement* r3 = rootRq.addChildRequirement( "R3", 3);

	BOOST_CHECKPOINT( "Test finding existing name");
	BOOST_CHECK_EQUAL( true, rootRq.hasChildWithName( "R2"));

	BOOST_CHECKPOINT( "Test finding inexistent name");
	BOOST_CHECK_EQUAL( false, rootRq.hasChildWithName( "Inexistent"));
}


//--------------------------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( testNameChanging)
{
	TestFixture fixture;
	Requirement rootRq( "Root");

	Requirement* r1 = rootRq.addChildRequirement( "R1", 1);
	Requirement* r2 = rootRq.addChildRequirement( "R2", 2);
	Requirement* r3 = rootRq.addChildRequirement( "R3", 3);

	BOOST_CHECKPOINT( "Test normal renaming");
	r1->setName( "N");
	// Check new name
	BOOST_CHECK_EQUAL( "N", r1->name());

	BOOST_CHECKPOINT( "Test duplicate name renaming");
	BOOST_CHECK_THROW( r1->setName( r2->name()), DuplicateNameError);

	BOOST_CHECKPOINT( "Test empty name renaming");
	BOOST_CHECK_THROW( r1->setName( ""), InvalidNameError);

	BOOST_CHECKPOINT( "Test renaming with no name change");
	// Action
	r1->setName( r1->name());


	BOOST_CHECKPOINT( "Test renaming of an orphan");
	// Make the orphan
	rootRq.removeChildRequirement( r1);
	r1->setName( "New Name");
	// Check new name
	BOOST_CHECK_EQUAL( "New Name", r1->name());

	delete r1;
}


//--------------------------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( testID)
{
	TestFixture fixture;
	Requirement rootRq( "ROOT");

	// Preparation of structures:
	// root
	//	r1
	//		r11

	Requirement* r1 = rootRq.addChildRequirement( "R1", 1);
	Requirement* r11 = r1->addChildRequirement( "R11", 1);

	BOOST_CHECKPOINT( "Testing second level requirement ID");
	BOOST_CHECK_EQUAL( "1.1", r11->id());

	BOOST_CHECKPOINT( "Testing first level requirement ID");
	BOOST_CHECK_EQUAL( "1", r1->id());

	BOOST_CHECKPOINT( "Testing root requirement ID");
	BOOST_CHECK_EQUAL( "", rootRq.id());
}


//--------------------------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( testIsAncestorOf)
{
	TestFixture fixture;
	Requirement rootRq( "ROOT");
	// root
	//   r1
	//     r11
	//     r12
	//   r2
	Requirement* r1 = rootRq.addChildRequirement( "R1", 1);
	Requirement* r11 = r1->addChildRequirement( "R11", 1);
	Requirement* r12 = r1->addChildRequirement( "R12", 2);
	Requirement* r2 = rootRq.addChildRequirement( "R2", 2);

	BOOST_CHECK( r1->isAncestorOf( *r11));
	BOOST_CHECK( r1->isAncestorOf( *r12));
	BOOST_CHECK( !r12->isAncestorOf( *r1));
	BOOST_CHECK( !r1->isAncestorOf( *r2));
	BOOST_CHECK( !r2->isAncestorOf( *r1));
	BOOST_CHECK( !r2->isAncestorOf( *r11));
	BOOST_CHECK( rootRq.isAncestorOf( *r11));
	BOOST_CHECK( !r11->isAncestorOf( rootRq));
}

//--------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testCommentInsertion)
{
	TestFixture fixture;
	Requirement rootRq( "ROOT");
	Requirement* r1 = rootRq.addChildRequirement( "R1", 1);

	BOOST_CHECKPOINT( "Test first insert");
	Comment com( "Me");
	r1->insertComment( com, 0);

	BOOST_CHECK( r1->commentsBegin() != r1->commentsEnd());
	BOOST_CHECK_EQUAL( com.authorName(), r1->commentsBegin()->authorName());

	BOOST_CHECKPOINT( "Insert at end");
	Comment com2( "Me again");

	r1->insertComment( com2, 10);
	BOOST_CHECK_EQUAL( com2.authorName(), (++r1->commentsBegin())->authorName());

	BOOST_CHECKPOINT( "Insert in the beginning");
	Comment com3( "Another me");
	r1->insertComment( com3, 0);
	BOOST_CHECK_EQUAL( com3.authorName(), r1->commentsBegin()->authorName());

	BOOST_CHECKPOINT( "Insert in the middle");
	Comment com4( "Yet another me");
	r1->insertComment( com4, 2);
	BOOST_CHECK_EQUAL( com4.authorName(), (++(r1->commentsBegin()))->authorName());
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testCommentRemoval)
{
	Requirement rootRq( "ROOT");
	rootRq.insertComment( Comment("C1"));
	rootRq.insertComment( Comment("C2"));
	rootRq.insertComment( Comment("C3"));
	rootRq.insertComment( Comment("C4"));
	rootRq.insertComment( Comment("C5"));

	BOOST_CHECKPOINT( "Testing removing at the beginning");
	rootRq.removeComment( 1);
	BOOST_CHECK_EQUAL( std::string("C2"), rootRq.commentsBegin()->authorName());

	BOOST_CHECKPOINT( "Testing removing at the end");
	rootRq.removeComment( 4);
	BOOST_CHECK_EQUAL( std::string("C4"), (--rootRq.commentsEnd())->authorName());

	BOOST_CHECKPOINT( "Testing removing in the middle");
	rootRq.removeComment( 2);
	BOOST_CHECK_EQUAL( std::string("C2"), rootRq.commentsBegin()->authorName());
	BOOST_CHECK_EQUAL( std::string("C4"), (++rootRq.commentsBegin())->authorName());
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testCommentChange)
{
	Requirement rootRq( "ROOT");
	rootRq.insertComment( Comment("C1"));
	rootRq.insertComment( Comment("C2"));
	rootRq.insertComment( Comment("C3"));
	rootRq.insertComment( Comment("C4"));
	rootRq.insertComment( Comment("C5"));

	BOOST_CHECKPOINT( "Testing changing at the beginning");
	rootRq.changeComment( Comment( "C1Ch"), 1);
	BOOST_CHECK_EQUAL( "C1Ch", rootRq.commentsBegin()->authorName());

	BOOST_CHECKPOINT( "Testing changing at the end");
	rootRq.changeComment( Comment( "C5Ch"), 5);
	BOOST_CHECK_EQUAL( "C5Ch", (--rootRq.commentsEnd())->authorName());

	BOOST_CHECKPOINT( "Testing changing in the middle");
	rootRq.changeComment( Comment( "C3Ch"), 3);
	BOOST_CHECK_EQUAL( "C3Ch", (++(++rootRq.commentsBegin()))->authorName());
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testCommentAt)
{
	Requirement rootRq( "ROOT");
	rootRq.insertComment( Comment("C1"));
	rootRq.insertComment( Comment("C2"));
	rootRq.insertComment( Comment("C3"));
	rootRq.insertComment( Comment("C4"));
	rootRq.insertComment( Comment("C5"));

	BOOST_CHECK_EQUAL( "C1", rootRq.commentAt(1).authorName());
	BOOST_CHECK_EQUAL( "C2", rootRq.commentAt(2).authorName());
	BOOST_CHECK_EQUAL( "C3", rootRq.commentAt(3).authorName());
	BOOST_CHECK_EQUAL( "C4", rootRq.commentAt(4).authorName());
	BOOST_CHECK_EQUAL( "C5", rootRq.commentAt(5).authorName());
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testMoveToPackage)
{
	Package package( "PACK");
	Requirement rootRq( "ROOT");
	// root
	//   r1
	//     r11
	//     r12
	//   r2
	Requirement* r1 = rootRq.addChildRequirement( "R1", 1);
	Requirement* r11 = r1->addChildRequirement( "R11", 1);
	Requirement* r12 = r1->addChildRequirement( "R12", 2);
	Requirement* r2 = rootRq.addChildRequirement( "R2", 2);

	rootRq.moveToPackage( &package);
	BOOST_CHECK_EQUAL( &package, rootRq.package());
	BOOST_CHECK_EQUAL( &package, r1->package());
	BOOST_CHECK_EQUAL( &package, r11->package());
	BOOST_CHECK_EQUAL( &package, r12->package());
	BOOST_CHECK_EQUAL( &package, r2->package());
}

// ---------------------------------------------------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE( testRemoveChild)
{
	Package package( "PACK");
	Requirement rootRq( "ROOT");
	// root
	//   r1
	//     r11
	//     r12
	//   r2
	Requirement* r1 = rootRq.addChildRequirement( "R1", 1);
	Requirement* r11 = r1->addChildRequirement( "R11", 1);
	Requirement* r12 = r1->addChildRequirement( "R12", 2);
	Requirement* r2 = rootRq.addChildRequirement( "R2", 2);

	BOOST_CHECKPOINT( "Remove existing");
	rootRq.removeChildRequirement( r1);
	BOOST_CHECK_EQUAL( 1, rootRq.numberOfChildren());
	BOOST_CHECK_EQUAL( 1, r2->position());
	BOOST_CHECK_EQUAL( r2, *rootRq.childrenBegin());

	BOOST_CHECKPOINT( "Add back removed child to a different position");
	rootRq.addChildRequirement( r1);
	BOOST_CHECK_EQUAL( 2, rootRq.numberOfChildren());
	BOOST_CHECK_EQUAL( r2, *rootRq.childrenBegin());
	BOOST_CHECK_EQUAL( r1, *(++rootRq.childrenBegin()));
	BOOST_CHECK_EQUAL( 1, r2->position());
	BOOST_CHECK_EQUAL( 2, r1->position());
}

} // namespace
